package org.lboot.ufos.client;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.HashUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.dialect.Props;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.IOUtils;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.lboot.ufos.api.BucketApi;
import org.lboot.ufos.api.FileApi;
import org.lboot.ufos.api.ObjectApi;
import org.lboot.ufos.api.VersionApi;
import org.lboot.ufos.config.OssProperties;
import org.lboot.ufos.config.S3OssConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author kindear
 * S3 协议存储相关操作
 */
@Slf4j
@Data
@Component
public class S3Client implements BucketApi, ObjectApi, FileApi, VersionApi {
    @Autowired
    OssProperties props;

    @Autowired
    S3OssConfig config;

    @Resource
    ApplicationContext context;


    // https://blog.csdn.net/qq_18300037/article/details/123795776
    private static final ConcurrentHashMap<String, AmazonS3> clients = new ConcurrentHashMap<>();

    // 服务终端地址
    private String endpoint;
    // 服务公钥
    private String accessKey;
    // 服务密钥
    private String secretKey;
    // 服务地址
    private String host;
    // 默认桶
    private String bucket;


    // 最大链接
    private Integer maxConnection = 100;
    // 区域
    private String region = "";
    // 预取path形式访问
    private Boolean enablePath = true;

    private AmazonS3 amazonS3 = null;

    // Bean 初始化构建方法
    @PostConstruct
    public void init(){
        if (Validator.isNotEmpty(props)){
            this.endpoint = props.getEndpoint();
            this.accessKey = props.getAccessKey();
            this.secretKey = props.getSecretKey();
            this.host = props.getHost();
            this.bucket = props.getBucketName();
        }else {
            log.warn("S3Client Inject failed");
        }
    }
    // Bean 执行过程中重载方法 -> 例如更换 endPoint、密钥等,需要在执行setXXX 后执行 reload
    @SneakyThrows
    public void reload(){
        String clientKey = clientHash();
        this.amazonS3 = clients.get(clientKey);
    }


    // 读取OSS配置文件路径 -> reload
    /**
     * 通过配置文件重载
     * @param propsPath
     */
    @SneakyThrows
    public void reload(String propsPath){
        // 获取后缀
        Props selfProps = new Props(propsPath);
        this.endpoint = selfProps.getProperty("oss.endpoint");
        this.accessKey = selfProps.getProperty("oss.public.key");
        this.secretKey = selfProps.getProperty("oss.private.key");
        this.host = selfProps.getProperty("oss.host");
        this.bucket = selfProps.getProperty("oss.bucket");
        reload();
    }

    @SneakyThrows
    public void reload(
            String endpoint,
            String accessKey,
            String secretKey,
            String host
    ){
        this.endpoint = endpoint;
        this.accessKey = accessKey;
        this.secretKey = secretKey;
        this.host = host;
        reload();
    }

    @SneakyThrows
    public String clientHash(){

        String calcStr = this.endpoint +
                this.accessKey +
                this.secretKey +
                this.host;
        return String.valueOf(HashUtil.fnvHash(calcStr));

    }

    // 初始化构建 AWSClient
    @SneakyThrows
    AmazonS3 clientBuild(){
        // 是否执行获取
        log.info("\n执行客户端构建");
        // 客户端配置，主要是全局的配置信息
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        clientConfiguration.setMaxConnections(this.maxConnection);
        // url以及region配置
        AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                this.endpoint, this.region);
        // 凭证配置
        AWSCredentials awsCredentials = new BasicAWSCredentials(this.accessKey, this.secretKey);
        AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
        // build amazonS3Client客户端
        this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
                .withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
                .disableChunkedEncoding().withPathStyleAccessEnabled(this.enablePath).build();
        //存入hash
        String clientKey = clientHash();
        clients.put(clientKey,this.amazonS3);
        return this.amazonS3;
    }
    // 获取已构建客户端
    @SneakyThrows
    AmazonS3 client(){
        if (Validator.isNotEmpty(this.amazonS3)){
            return this.amazonS3;
        }
        return clientBuild();
    }

    @Override
    @SneakyThrows
    public boolean createBucket(String bucketName) {
        AmazonS3 client = client();
        if (doesBucketExist(bucketName)){
            return false;
        }
        client.createBucket(bucketName);
        return true;
    }

    @Override
    public AccessControlList getBucketACL(String bucketName) {
        AmazonS3 client = client();
        return client.getBucketAcl(bucketName);
    }

    @Override
    @SneakyThrows
    public boolean doesBucketExist(String bucketName) {
        AmazonS3 client = client();
        return client.doesBucketExistV2(bucketName);
    }

    @Override
    @SneakyThrows
    public List<Bucket> listBuckets() {
        AmazonS3 client = client();
       return client.listBuckets();
    }

    @Override
    @SneakyThrows
    public List<String> listBucketsName() {
        List<Bucket> buckets = listBuckets();
        return buckets.stream().map(Bucket::getName).collect(Collectors.toList());
    }

    @Override
    @SneakyThrows
    public boolean deleteBucket(String bucketName) {
        AmazonS3 client = client();
        client.deleteBucket(bucketName);
        return true;
    }

    @Override
    @SneakyThrows
    public boolean setBucketPrivate(String bucketName) {
        AmazonS3 client = client();
        client.setBucketAcl(bucketName, CannedAccessControlList.Private);
        return true;
    }

    @Override
    @SneakyThrows
    public boolean setBucketPublic(String bucketName) {
        AmazonS3 client = client();
        client.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite);
        return true;
    }

    @Override
    @SneakyThrows
    public boolean setBucketPublicRead(String bucketName) {
        AmazonS3 client = client();
        client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
        return true;
    }

    // 重写 ObjectAPI


    @Override
    @SneakyThrows
    public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) {
        // 构建写入对象信息
        AmazonS3 client = client();
        byte[] bytes = IOUtils.toByteArray(stream);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        // 设置meta
        objectMetadata.setContentLength(size);
        objectMetadata.setContentType(contextType);
        // 设置自定义时间
//        String now = DateUtil.now();
//        objectMetadata.addUserMetadata("modifyDate", now);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        // 获取
        ObjectMetadata metaBefore = null;
        // 判断是否开启版本控制
        if (config.enableVersion()){
            // 判断当前对象的基础信息
            if (isExistObject(bucketName, objectName)){
                metaBefore = getObjectInfo(bucketName, objectName);
                // 获取上次修改时间
                Date lastModified = metaBefore.getLastModified();
                // 获取当前时间
                Date currentTime = DateUtil.date();
                // 计算两者时间间隔
                long differenceInSeconds = DateUtil.between(lastModified, currentTime, DateUnit.SECOND);
                // 大于间隔时间生成一个复制版本
                if (differenceInSeconds >= config.versionIntervalTime()){
                    // 构建临时前缀
                    String objPrefix = StrUtil.subBefore(objectName,".",true);
                    // 获取文件拓展名
                    String extName = FileUtil.extName(objectName);
                    // 复制临时
                    ObjectListing objectListing = getObjectsByPrefix(bucketName, objPrefix);
                    int count = objectListing.getObjectSummaries().size();
                    String versionObject = objPrefix+"@" + count + "." + extName;
                    // 复制
                    copyObject(bucketName, objectName, bucketName, versionObject);
                    // 设置新的版本
                    objectMetadata.addUserMetadata("version", StrUtil.format("{}",count + 1));
                    PutObjectResult result = client.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
                    ObjectMetadata metaAfter = result.getMetadata();
                    // 如果没有变化，清理复制的版本
                    if (metaBefore.getETag().equals(metaAfter.getETag())){
                        // 复制回来
                        copyObject(bucketName, versionObject, bucketName, objectName);
                        // 清理
                        deleteObject(bucketName, versionObject);
                    }
                    return result;
                }
            }
        }
        // 设置自定义meta
        objectMetadata.addUserMetadata("version", "1");
        PutObjectResult result = client.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
        return result;
    }

    @Override
    @SneakyThrows
    public S3Object getObject(String bucketName, String objectName) {
        AmazonS3 client = client();
        return client.getObject(bucketName, objectName);
    }

    @Override
    @SneakyThrows
    public boolean doesObjectExist(String bucketName, String objectName) {
        AmazonS3 client = client();
        return client.doesObjectExist(bucketName,objectName);
    }

    @Override
    @SneakyThrows
    public boolean deleteObject(String bucketName, String objectName) {
        AmazonS3 client = client();
        client.deleteObject(bucketName, objectName);
        return true;
    }

    @Override
    public DeleteObjectsResult deleteObjectsByPrefix(String bucketName, String prefix) {
        AmazonS3 client = client();
        // 列出符合指定前缀的所有对象
        ObjectListing objectListing = client.listObjects(bucketName, prefix);
        List<S3ObjectSummary> objectsToDelete = objectListing.getObjectSummaries();

        // 构造删除请求
        List<DeleteObjectsRequest.KeyVersion> keys = new ArrayList<>();
        for (S3ObjectSummary objectSummary : objectsToDelete) {
            keys.add(new DeleteObjectsRequest.KeyVersion(objectSummary.getKey()));
        }

        // 批量删除对象
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName).withKeys(keys);
        DeleteObjectsResult deleteObjects = client.deleteObjects(deleteObjectsRequest);
        return deleteObjects;
    }

    @Override
    @SneakyThrows
    public ObjectMetadata getObjectMetadata(String bucketName, String objectName) {
        AmazonS3 client = client();
        return client.getObjectMetadata(bucketName, objectName);
    }

    @Override
    @SneakyThrows
    public CopyObjectResult copyObject(String sourceBucket, String sourceObject, String targetBucket, String targetObject) {
        AmazonS3 client = client();
        return client.copyObject(sourceBucket, sourceObject, targetBucket, targetObject);
    }

    @Override
    @SneakyThrows
    public ObjectListing listObjects(String bucketName) {
        AmazonS3 client = client();
        return client.listObjects(bucketName);
    }


    @Override
    @SneakyThrows
    public ObjectListing listObjects(ListObjectsRequest request) {
        AmazonS3 client = client();
        return client.listObjects(request);
    }




    @Override
    @SneakyThrows
    public String getObjectURL(String bucketName, String objectName, Integer expires) {
        AmazonS3 client = client();
        Date date = new Date();
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, expires);
        URL url = client.generatePresignedUrl(bucketName, objectName, calendar.getTime());
        return url.toString();
    }

    /**
     * 文件下载
     * @param bucketName
     * @param objectName
     * @return
     */
    @SneakyThrows
    public ResponseEntity<byte[]> download(String bucketName, String objectName){
        InputStream stream = getObjectStream(bucketName, objectName);
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取文件名
        String fileName = FileUtil.getName(objectName);
        // 文件下载
        return downloadMethod(stream, request, fileName);
    }
    /**
     * 文件预览
     * @param bucketName
     * @param objectName
     * @return
     */
    @SneakyThrows
    public ResponseEntity<byte[]> preview(String bucketName, String objectName){
        InputStream stream = getObjectStream(bucketName, objectName);
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取文件名
        String fileName = FileUtil.getName(objectName);
        // 文件下载
        return previewMethod(stream, request, fileName);
    }

    @SneakyThrows
    @Override
    public void enableVersion(String bucketName) {
        // 是否允许开启版本控制
        if (!config.enableVersion()){
            log.warn("禁止开启版本控制");
            return;
        }
        AmazonS3 client = client();
        // 启用版本控制
        BucketVersioningConfiguration versioningConfig = new BucketVersioningConfiguration()
                .withStatus(BucketVersioningConfiguration.ENABLED);

        SetBucketVersioningConfigurationRequest setBucketVersioningConfigurationRequest =
                new SetBucketVersioningConfigurationRequest(bucketName, versioningConfig);

        // 设置存储桶的版本控制配置
        client.setBucketVersioningConfiguration(setBucketVersioningConfigurationRequest);
    }
}
